package br.ufc.customJDBC.routing;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

import br.ufc.custom.multiHistogram.MultiHistogram;
import br.ufc.customJDBC.CustomConnection;
import br.ufc.customJDBC.csv.CSVLog;
import br.ufc.util.WrapperResultSet;

public class MultiHistogramRouter implements Router {


	private List<CustomConnection> connectionPool;
	private HashMap<Integer, CustomConnection> connectionsID;
	private MultiHistogram multiHistogram;
	private CSVLog csv;

	public MultiHistogramRouter(List<CustomConnection> connectionPool, MultiHistogram multiHistogram, CSVLog csv ){
		this.connectionPool = connectionPool;
		this.connectionsID = new HashMap<Integer, CustomConnection>();
		this.multiHistogram = multiHistogram;
		this.csv = csv;

		int i = 0;
		for (CustomConnection connection : connectionPool) {
			connectionsID.put(i, connection);
			System.out.println(i + ": " + connection.getIp());
			i ++;
		}
	}
	@Override
	public boolean execute(String sql) {

		boolean ret = false;
		List<ExecuteThread> threadPool = new ArrayList<ExecuteThread>();
		List<CustomConnection> connections = getConnectionFromHistogram(sql);

		long startTime = System.nanoTime();
		for(CustomConnection cc : connections){
			ExecuteThread thread = new ExecuteThread(sql, cc);
			threadPool.add(thread);
			thread.start();			
		}
		try {
			for(ExecuteThread thread : threadPool){
				thread.join();
				if(thread.getReturn() == true){
					ret = true;
				}
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		long stopTime = System.nanoTime();

		String nodes = "[";
		for(CustomConnection con : connections){
			nodes += con.getIp() + " ";
		}
		nodes += "]";

		csv.writeLine(sql, (stopTime - startTime), 1, "MultiExecute", nodes );
		return ret;
	}

	@Override
	public ResultSet executeQuery(String sql) throws SQLException {

		List<ResultSet> resultSets = new ArrayList<ResultSet>(); 
		List<ExecuteQueryThread> threadPool = new ArrayList<ExecuteQueryThread >();
		List<CustomConnection> connections = getConnectionFromHistogram(sql);

		long startTime = System.nanoTime();
		for(CustomConnection cc : connections){
			//			System.out.println(cc);
			ExecuteQueryThread thread = new ExecuteQueryThread(sql, cc);
			threadPool.add(thread);
			//			thread.setUncaughtExceptionHandler(h);
			thread.start();			
		}
		try {
			for(ExecuteQueryThread thread : threadPool){
				thread.join();
				resultSets.add(thread.getReturn());					
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		long stopTime = System.nanoTime();
		String nodes = "[";
		for(CustomConnection con : connections){
			nodes += con.getIp() + " ";
		}
		nodes += "]";

		csv.writeLine(sql, (stopTime - startTime), 1 , "MultiExecuteQuery", nodes );

		return WrapperResultSet.createResultSets(resultSets);
	}

	@Override
	public int executeUpdate(String sql) throws SQLException {
		int ret = 0;
		System.out.println("executeUpdate");
		System.out.println(sql);
		List<ExecuteUpdateThread> threadPool = new ArrayList<ExecuteUpdateThread>();
		List<CustomConnection> connections = getConnectionFromHistogram(sql);

		long startTime = System.nanoTime();
		for(CustomConnection cc : connections){
			System.out.println(cc.getIp());
			ExecuteUpdateThread thread = new ExecuteUpdateThread(sql, cc);
			threadPool.add(thread);
			thread.start();			
		}
		try {
			for(ExecuteUpdateThread thread : threadPool){
				thread.join();				
				ret = thread.getReturn();				
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		long stopTime = System.nanoTime();

		String nodes = "[";
		for(CustomConnection con : connections){
			nodes += con.getIp() + " ";
		}
		nodes += "]";

		csv.writeLine(sql, (stopTime - startTime), 1, "MultiExecute", nodes );
		return ret;	
	}

	private List<CustomConnection> getConnectionFromHistogram(String sql){
		Collection<Integer> ids = null; 
		try{
			ids = multiHistogram.getPartitionsForStatement(sql);
		}catch(Exception e){
			ids = Arrays.asList(2);
		}
		List<CustomConnection> connections = new ArrayList<CustomConnection>();
		for(int id : ids){
			connections.add(connectionsID.get(id));
			//			System.out.println(id + " " + connectionsID.get(id));
		}
		return connections;
	}
}

class ExecuteThread extends Thread{

	private String sql;
	CustomConnection cc;
	boolean ret;

	public ExecuteThread(String sql, CustomConnection cc){
		this.sql = sql;
		this.cc = cc;
	}

	public void run() {	
		try {
			Statement statement = cc.createStatement();
			ret = statement.execute(sql);
			statement.close();
			this.cc.commit();
		} catch (SQLException e) {
			e.printStackTrace();
		}		
	}

	public boolean getReturn(){
		return ret;
	}
}

class ExecuteQueryThread extends Thread{

	private String sql;
	CustomConnection cc;
	ResultSet rs;

	public ExecuteQueryThread(String sql, CustomConnection cc){
		this.sql = sql;
		this.cc = cc;
	}

	public void run() {	
		try {
			Statement statement = cc.createStatement();			
			rs = statement.executeQuery(sql);
			//			statement.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}		
	}

	public ResultSet getReturn(){
		return rs;
	}
}

class ExecuteUpdateThread extends Thread{

	private String sql;
	CustomConnection cc;
	int ret;

	public ExecuteUpdateThread(String sql, CustomConnection cc){
		this.sql = sql;
		this.cc = cc;
	}

	public void run() {	
		try {
			Statement statement = cc.createStatement();			
			ret = statement.executeUpdate(sql);
			statement.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}		
	}

	public int getReturn(){
		return ret;
	}
}